/**
* file name  : it/ciano/jenoware/Sbj.java
* authors    : Luciano Xumerle
* created    : dom 05 set 2004 10:16:27 CEST
*
* Copyright (c) 2004-2010 Luciano Xumerle. All rights reserved.
*
* This file is part of gephinfo.
*
* gephinfo is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.
*
*/

package it.ciano.jenoware;

import java.util.*;

/**
 * The class manages a single subject and the informations
 * about pedigree, phenotypes and genotypes.
 *
 * @author Luciano Xumerle
 * @version 0.0.1
 */
public class Sbj extends ID {
    // store pedigree
    private SbjFml _fams;
    // sex (1 male 2 female)
    private Value _sex;
    // fenotype list (height, etc)
    private HashMap _phenotype = null;
    // marker alleles
    private HashMap _genotype = null;
    // is set to true if subject have almost 1 genotype not null
    private boolean _isGenotyped;
    // is faked sbj
    private boolean _isFaked;
    // is a control??
    private boolean _isControl;

    /**
     * Class constructor
     *
     * @param id  An unique identifier ([string]integer[.integer][string])
     * @param fml the unique family number
     * @param sbj the unique subject number into the family
     * @param fth the father number into the family
     * @param mth the mother number into the family
     * @param sex a number for the sex (1=male, 2=female)
     * @param prb is the subject a proband? 1=yes, 0=no
     */
    public Sbj ( String id, String fml, String sbj, String fth, String mth, String sex, String prb ) {
        super ( id );
        _fams=new SbjFml ();
        setFML ( fml, sbj, fth, mth, prb );

        _sex = new Value ( sex, false );
        _phenotype = new HashMap();
        _genotype = new HashMap();

        _isGenotyped = false;
        _isFaked = false;
        _isControl = false; // default = sbj is a case
    }

    /**
     * The empty class constructor.
     *
     * @param id the unique identifier into the total set.
     */
    public Sbj ( String id ) {
        this ( id, "", "", "", "", "", "" );
    }

    /**
     * Set the family pedigree identifier.
     *
     * @param fml the family pedigree identifier.
     */
    public void setFML ( String fml ) {
        _fams.addFam ( fml );
    }

    /**
     * Set the family pedigree identifier.
     *
     * @param fml the family pedigree identifier.
     */
    public void setFML ( String fml, String sbj, String fth, String mth, String prb ) {
        if ( ! fml.equals ( "" ) )
            _fams.addFam ( fml, sbj, fth, mth, prb );
    }

    /**
     * Returns the family pedigree identifier.
     *
     * @return the family pedigree identifier.
     */
    public String[] getFML() {
        return _fams.getFamilies();
    }

    /**
     * Returns True or False.
     *
     * @return True or False.
     */
    public boolean hasFamily() {
        return ( _fams.size() >0 );
    }

    /**
     * Returns True or False.
     *
     * @param fam the family ID.
     * @return True or False.
     */
    public boolean existFamily ( String fam ) {
        return _fams.containsKey ( fam );
    }

    /**
     * Set the subject pedigree identifier.
     *
     * @param sbj the subject pedigree identifier.
     */
    public void setSBJ ( String fml, String sbj ) {
        _fams.setSubjectID ( fml, sbj );
    }

    /**
      * Returns the subject pedigree identifier.
      *
      * @return the subject pedigree identifier.
      */
    public String getSBJ ( String fml ) {
        return _fams.getSubjectID ( fml );
    }

    /**
     * Set the father pedigree identifier.
     *
     * @param fth the father pedigree identifier.
     */
    public void setFTH ( String fml, String fth ) {
        _fams.setFatherID ( fml, fth );
    }

    /**
     * Returns the father pedigree identifier.
     *
     * @return the father pedigree identifier.
     */
    public String getFTH ( String fml ) {
        return _fams.getFatherID ( fml );
    }

    /**
     * Set the mother pedigree identifier.
     *
     * @param mth the mother pedigree identifier.
     */
    public void setMTH ( String fml, String mth ) {
        _fams.setMotherID ( fml, mth );
    }

    /**
     * Returns the mother pedigree identifier.
     *
     * @return the mother pedigree identifier.
     */
    public String getMTH ( String fml ) {
        return _fams.getMotherID ( fml );
    }

    /**
     * Set the sex of subject (1=male, 2=female).
     *
     * @param sex the sex of subject.
     */
    public void setSEX ( String sex ) {
        _sex = new Value ( sex, false );
    }

    /**
     * Returns the sex.
     *
     * @return the sex.
     */
    public String getSEX() {
        return _sex.getValue();
    }

    /**
     * Returns true if sex equals "1" (male).
     *
     * @return true or false.
     */
    public boolean isMale() {
        return _sex.getValue().equals ( "1" );
    }

    /**
    * Returns true if sex equals "2" (female).
    *
    * @return true or false.
    */
    public boolean isFemale() {
        return _sex.getValue().equals ( "2" );
    }

    /**
     * Set the subject as proband (1) or not (0).
     *
     * @param prb the proband value (1=proband, 0=not proband).
     */
    public void setProband ( String fml, String prb ) {
        _fams.setProband ( fml, prb );
    }

    /**
     * Returns the value of the proband property.
     *
     * @return the value of the proband property.
     */
    public String getProband ( String fml ) {
        return _fams.getProband ( fml );
    }

    /**
     * Returns true if the subject is a proband.
     *
     * @return true or false.
     */
    public boolean isProband ( String fml ) {
        return _fams.getProband ( fml ).equals ( "1" );
    }

    /**
     * Set the Sbj as a faked Sbj.
     * A faked Sbj is added automatically to complete a family.
     */
    public void setAsFaked() {
        _isFaked = true;
    }

    /**
     * Returns true if the subject is faked.
     * A faked Sbj is added automatically to complete a family.
     *
     * @return true or false.
     */
    public boolean isFaked() {
        return _isFaked;
    }

    /**
     * Set the subject as a control.
     * The subject is a case by default.
     */
    public void setAsControl() {
        _isControl = true;
    }

    /**
     * Returns true if this Sbj is a case (defaults).
     *
     * @return true or false.
     */
    public boolean isCase() {
        return !_isControl;
    }

    /**
     * Returns true if this Sbj is a control.
     * The subject is a case by default.
     *
     * @return true or false.
     */
    public boolean isControl() {
        return _isControl;
    }

    /**
     * Returns the faked father or mother.
     * A faked parents is used to correct incomplete families into Pedigree.
     *
     * @param parentSex the value are "1" (the faked father) or "2" (the faked mother).
     * @return the required faked Sbj (null if sex isn't valid).
     */
    public Sbj getFakedParentSBJ ( String fml, String parentSex ) {
        String sbjid;
        if ( parentSex.equals ( "2" ) )
            sbjid = getMTH ( fml );
        else if ( parentSex.equals ( "1" ) )
            sbjid = getFTH ( fml );
        else
            return null;
        StringBuffer id = new StringBuffer ( "ff" );
        id.append ( fml );
        id.append ( "." );
        id.append ( sbjid );
        return new Sbj ( id.toString(), fml, sbjid, "", "", parentSex, "0" );
    }

    /**
     * Adds the phenotype.
     *
     * @param name the phenotype name.
     * @param value the phenotype value.
     */
    public void addPhenotype ( String name, String value ) {
        addPhenotype ( new Phenotype ( name, value ) );
    }

    /**
     * Adds the Phenotype objects.
     *
     * @param pheno the Phenotype object.
     */
    public void addPhenotype ( Phenotype pheno ) {
        if ( pheno.isValid() ) {
            Phenotype old = getPhenotype ( pheno.getName() );
            if ( old.isValid() && !old.equals ( pheno ) )
                System.err.println ( "Duplicate Phenotype \"" + old.getName()
                        + "\" with ID \"" + getID()
                        + "\" from " + old.getValue()
                        + " to " + pheno.getValue() );
            _phenotype.put ( pheno.getName(), pheno );
        }
    }

    /**
     * Adds every Phenotype object from another Sbj object.
     *
     * @param src Sbj object.
     */
    public void addPhenotypesFrom ( Sbj src ) {
        String pheno[] = src.getPhenotypesList();
        if ( pheno != null )
            for ( int i = 0; i < pheno.length; i++ )
                addPhenotype ( src.getPhenotype ( pheno[ i ] ) );
    }

    /**
     * Returns the Phenotype object having the required name.
     *
     * @param name the phenotype name.
     * @return the Phenotype object.
     */
    public Phenotype getPhenotype ( String name ) {
        if ( !_phenotype.containsKey ( name ) )
            return new Phenotype ( name, "" );
        return ( Phenotype ) _phenotype.get ( name );
    }

    /**
     * Adds the genotype.
     *
     * @param name the genotype name.
     * @param allele1 the first allele.
     * @param allele2 the second allele.
     */
    public void addGenotype ( String name, String allele1, String allele2 ) {
        addGenotype ( new Genotype ( name, allele1, allele2 ) );
    }

    /**
     * Adds the genotype.
     *
     * @param name the genotype name.
     * @param allele1 the first allele.
     * @param allele2 the second allele.
     * @param date the date.
     */
    public void addGenotype ( String name, String allele1, String allele2, String date ) {
        addGenotype ( new Genotype ( name, allele1, allele2, date ) );
    }

    /**
     * Adds the genotype.
     *
     * @param name the genotype name.
     * @param allele1 the first allele.
     * @param allele2 the second allele.
     * @param date the date.
     */
    public void addGenotype ( String name, Allele allele1, Allele allele2, String date ) {
        addGenotype ( new Genotype ( name, allele1, allele2, date ) );
    }

    /**
     * Adds the Genotype object.
     *
     * @param geno the Genotype object.
     */
    public void addGenotype ( Genotype geno ) {
        if ( geno != null ) {
            Genotype old = getGenotype ( geno.getName() );
            if ( geno.isValid() ) {
                if ( old.isValid() && !old.equals ( geno ) )
                    System.err.println ( "Duplicate Genotype \"" + old.getName()
                            + "\" with ID \"" + getID()
                            + "\" from " + old.getConcatAlleles()
                            + " to " + geno.getConcatAlleles() );
                _genotype.put ( geno.getName(), geno );
                _isGenotyped = true;
            }
        }
    }

    /**
     * Adds every Genotype object from another Sbj object.
     *
     * @param src Sbj object.
     */
    public void addGenotypeFrom ( Sbj src ) {
        String markers[] = src.getMarkersList();
        if ( markers != null )
            for ( int i = 0; i < markers.length; i++ )
                addGenotype ( src.getGenotype ( markers[ i ] ) );
    }

    /**
     * Returns the Genotype object having the required name.
     *
     * @param name the marker name.
     * @return the Genotype object.
     */
    public Genotype getGenotype ( String name ) {
        if ( !_genotype.containsKey ( name ) )
            return new Genotype ( name );
        return ( Genotype ) _genotype.get ( name );
    }

    /**
     * Return the String with id, genotype name, and genotyope alleles.
     *
     * @param marker the marker name.
     * @return the string.
     */
    public String getGenotypeInfo ( String marker ) {
        return "id: " + getID() + " " + getGenotype ( marker ).toString();
    }

    /**
     * Returns the list of phenotypes.
     *
     * @return the list of phenotypes (null if there is no phenotype).
     */
    public String[] getPhenotypesList() {
        if ( _phenotype.entrySet().size() > 0 ) {
            String a[] = new String[ _phenotype.entrySet().size() ];
            int i = 0;
            for ( Iterator it = _phenotype.entrySet().iterator(); it.hasNext(); ) {
                Map.Entry entry = ( Map.Entry ) it.next();
                a[ i++ ] = ( String ) entry.getKey();
            }
            return a;
        }
        return null;
    }

    /**
     * Returns the list of marker.
     *
     * @return the list of markers (null if there is no markers).
     */
    public String[] getMarkersList() {
        if ( isGenotyped() ) {
            String a[] = new String[ _genotype.entrySet().size() ];
            int i = 0;
            for ( Iterator it = _genotype.entrySet().iterator(); it.hasNext(); ) {
                Map.Entry entry = ( Map.Entry ) it.next();
                a[ i++ ] = ( String ) entry.getKey();
            }
            return a;
        }
        return null;
    }

    /**
     * Returns true if the subject has almost a valid genotype.
     *
     * @return true or false.
     */
    public boolean isGenotyped() {
        return ( _isGenotyped && !isFaked() );
    }

    /**
     * Returns true if the marker name has a valid Genotype.
     *
     * @param marker the marker name.
     * @return true or false.
     */
    public boolean isValid ( String marker ) {
        return getGenotype ( marker ).isValid();
    }

    /**
     * Overriding toString method.
     *
     * @return the String.
     */
    public String toString() {
        return getID();
    }

    /**
     * Overriding equals method.
     *
     * @param o
     * @return true if the fields id, fml, sbj are equals.
     */
    public boolean equals ( Object o ) {
        Sbj s = ( Sbj ) o;
        return ( super.equals ( s ) );
    }

    /**
     *  Overriding compareTo method.
     *
     * @param o the object to compare
     * @return int with 0, -1 or 1
     */
    public int compareTo ( Object o ) {
        Sbj other = ( Sbj ) o;
        if ( hasFamily() &&  other.hasFamily()   ) {
            String[] t = getFML();
            String[] e = other.getFML();
            // if the family is the same
            for ( int i=0; i<t.length; i++ )
                for ( int j=0; j<e.length; j++ )
                    if (  t[i].equals ( e[j] )  ) {
                        String st = getSBJ ( t[i] );
                        String et = other.getSBJ ( t[i] );
                        return st.compareTo ( et );
                    }
            // the families are distinct
            return t[0].compareTo ( e[0] );
        }
        // compare ID
        return super.compareTo ( other );
    }

    /**
     * Adds all missing informations contained in source Sbj to this Sbj.
     *
     * @param o the source subject.
     */
    public void merge ( Sbj o ) {
        if ( !getID().equals ( o.getID() )  )
            return;
        addPhenotypesFrom ( o );
        addGenotypeFrom ( o );

        String[] n=o.getFML();
        if ( n != null )
            for ( int ff=0; ff<n.length; ff++ )
                setFML ( n[ff], o.getSBJ ( n[ff] ), o.getFTH ( n[ff] ),
                        o.getMTH ( n[ff] ), o.getProband ( n[ff] ) );
    }


} // end class

